#include "stdafx.h"
#include "HXShader.h"
#include "HXUtil.h"
#include "HXWindow.h"

//-----------------------------------------------------------------------------
// shader support functions

bool ReadTextFile(string& text, const wstring& filename) 
{
	FILE *fp;
	if (_wfopen_s(&fp, filename.c_str(), L"r") == 0)
	{
		if (fp != NULL) 
		{
			fseek(fp, 0, SEEK_END);
       		long count = ftell(fp);
       		rewind(fp);
			if (count > 0) 
			{
				char* buffer = new char[count+1];
				count = fread(buffer, sizeof(char), count, fp);
				buffer[count] = '\0';
				text = buffer;
				delete [] buffer;
			}
			fclose(fp);				
		}
		return true;
	}
	else
	{
		g_window.ShowErrorStatus(L"File \"%s\" cannot be opened for reading.", filename.c_str());
		return false;
	}
}

void PrintShaderInfoLog(GLuint obj)
{
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;

	glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);

    if (infologLength > 0)
    {
        infoLog = (char *)malloc(infologLength);
        glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
		ANotify(NULL, "Shader Info Log:\n%s", infoLog);
        free(infoLog);
    }
}

void PrintProgramInfoLog(GLuint obj)
{
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;

	glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infologLength);

    if (infologLength > 0)
    {
        infoLog = (char *)malloc(infologLength);
        glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog);
		ANotify(NULL, "Program Info Log:\n%s", infoLog);
        free(infoLog);
    }
}

//-----------------------------------------------------------------------------

HXShader::HXShader() :
	m_progId(0)
{
}

HXShader::~HXShader()
{
	Destroy();
}

void HXShader::Destroy()
{
	for (size_t i = 0; i < m_vertexShaders.size(); i++) 
	{
		glDeleteShader(m_vertexShaders[i]);
	}
	m_vertexShaders.clear();
	for (size_t i = 0; i < m_fragmentShaders.size(); i++) 
	{
		glDeleteShader(m_fragmentShaders[i]);
	}
	m_fragmentShaders.clear();
	if (m_progId) 
	{
		glDeleteProgram(m_progId);
		m_progId = 0;
	}
}

bool HXShader::Link()
{
	m_progId = glCreateProgram();

    for (size_t i = 0; i < m_vertexShaders.size(); i++) 
	{
        glAttachShader(m_progId, m_vertexShaders[i]);
    }

    for (size_t i = 0; i < m_fragmentShaders.size(); i++) 
	{
        glAttachShader(m_progId, m_fragmentShaders[i]);
    }

    glLinkProgram(m_progId);

    GLint success = 0;
    glGetProgramiv(m_progId, GL_LINK_STATUS, &success);

	return success != 0;
}

bool HXShader::SetUniform(const string& name, GLfloat* val, int count)
{
	GLint id = glGetUniformLocation(m_progId, name.c_str());
	if (id == -1) 
	{
		return false;
	}
	switch (count) 
	{
		case 1:
			glUniform1fv(id, 1, val);
			break;
		case 2:
			glUniform2fv(id, 1, val);
			break;
		case 3:
			glUniform3fv(id, 1, val);
			break;
		case 4:
			glUniform4fv(id, 1, val);
			break;
	}
	return true;
}

bool HXShader::SetTextureUnit(const string& texname, int texunit)
{
	GLint linked;
	glGetProgramiv(m_progId, GL_LINK_STATUS, &linked);
	if (linked != GL_TRUE) 
	{
		return false;
	}
	GLint id = glGetUniformLocation(m_progId, texname.c_str());
	if (id == -1) 
	{
		return false;
	}
	glUniform1i(id, texunit);
	return true;
}

bool HXShader::BindTexture(GLenum target, const string& texname, GLuint texid, int texunit)
{
	glActiveTexture(GL_TEXTURE0 + texunit);
	glBindTexture(target, texid);
	if (SetTextureUnit(texname, texunit))
	{
		glActiveTexture(GL_TEXTURE0);
		return true;
	}
	return false;
}

bool HXShader::SetFloat(const string& varname, float value)
{
	GLint linked;
	glGetProgramiv(m_progId, GL_LINK_STATUS, &linked);
	if (linked != GL_TRUE) 
	{
		return false;
	}
	GLint id = glGetUniformLocation(m_progId, varname.c_str());
	if (id == -1) 
	{
		return false;
	}
	glUniform1f(id, value);
	return true;
}


GLuint HXShader::CompileShader(GLenum target, const char* shader)
{
    GLuint object = glCreateShader( target);

    if (!object)
        return object;

    glShaderSource(object, 1, &shader, NULL);
    glCompileShader(object);

    // check if shader compiled
    GLint compiled = 0;
    glGetShaderiv(object, GL_COMPILE_STATUS, &compiled);

    if (!compiled)
    {
        glDeleteShader( object);
        return 0;
    }

    return object;
}

bool HXShader::AttachShaderFromFile(const wstring& filename, GLenum targetId, vector<GLuint>& targetArray)
{
	if (filename.size())
	{
		string shaderSrc;
		if (ReadTextFile(shaderSrc, filename))
		{
			return AttachShaderFromString(shaderSrc, targetId, targetArray);
		}
	}
	return false;
}

bool HXShader::AttachShaderFromString(const string& source, GLenum targetId, vector<GLuint>& targetArray)
{
	GLuint shaderId = CompileShader(targetId, source.c_str());
	if (shaderId)
	{
		targetArray.push_back(shaderId);
		return true;
	}
	return false;
}